﻿using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Net;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Touch;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Time;

using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using GHI.Premium.Net;
using Gadgeteer;

using GadgeteerLibrary;
using Gadgeteer.Modules.GHIElectronics;
using Microsoft.SPOT.Net.NetworkInformation;

namespace EthernetTest
{
    public partial class Program
    {
        private Timer temperatureCheckTimer;
        private Timer sendTimer;
        private string firstTime;
        private string lastTime;
        private RESTClient restClient;
        private ArrayList sensorData;
        private static bool isNetworkUp = false;
        private static bool isRegistered = false;
        private static bool timeSynchronized = false;
        private static bool isStoredBefore = false;
        private const int payloadSize = 2;

        // initialization
        Program()
        {
            string apiKey = "122f522f-3114-499f-a7f8-749185150910";
            Device device = new Device(1047, "GadgeteerDevice");
            ArrayList sensors = new ArrayList();
            sensors.Add(new Sensor("MyTempSensor1", SensorType.temperatureHumidity, "GHI", 2000, 4000, 60));
            sensors.Add(new Sensor("MyTempSensor2", SensorType.temperatureHumidity, "GHI", 2000, 4000, 60));
            restClient = new RESTClient(apiKey, device, sensors);
            sensorData = new ArrayList();
            firstTime = lastTime = string.Empty;
        }

        // This method is run when the mainboard is powered up or reset.   
        void ProgramStarted()
        {
            // Use Debug.Print to show messages in Visual Studio's "Output" window during debugging.
            Debug.Print("Program Started");
            this.InitializeNetwork();
            this.InitializeSDCard();
            this.InitializeSensor();
        }

        void InitializeNetwork()
        {
            if (!ethernet_J11D.Interface.IsOpen)
                ethernet_J11D.Interface.Open();

            if (!ethernet_J11D.Interface.NetworkInterface.IsDhcpEnabled)
                ethernet_J11D.Interface.NetworkInterface.EnableDhcp();

            NetworkInterfaceExtension.AssignNetworkingStackTo(ethernet_J11D.Interface);

            // setup events
            ethernet_J11D.Interface.CableConnectivityChanged += Interface_CableConnectivityChanged;
            ethernet_J11D.Interface.NetworkAddressChanged +=Interface_NetworkAddressChanged;
            ethernet_J11D.NetworkUp += ethernet_J11D_NetworkUp;
            ethernet_J11D.NetworkDown += ethernet_J11D_NetworkDown;

            if (!ethernet_J11D.Interface.NetworkInterface.IsDhcpEnabled)
                ethernet_J11D.Interface.NetworkInterface.EnableDhcp();

            this.NetworkConnect();

            try
            {
                if (isNetworkUp)
                {
                    // after connecting, set up local time and register the sensors.
                    this.SetLocalTime();
                    this.RegisterSensor();
                }
            }
            catch (Exception e)
            {
                Debug.Print(e.Message);
            }
        }

        void ethernet_J11D_NetworkDown(GTM.Module.NetworkModule sender, GTM.Module.NetworkModule.NetworkState state)
        {
            Debug.Print("Network down.");
        }

        void ethernet_J11D_NetworkUp(GTM.Module.NetworkModule sender, GTM.Module.NetworkModule.NetworkState state)
        {
            Debug.Print("Network up.");
        }

        void Interface_CableConnectivityChanged(object sender, EthernetBuiltIn.CableConnectivityEventArgs e)
        {
            if (!e.IsConnected)
            {
                Debug.Print("Cable is not connected!");
                isNetworkUp = false;
            }
        }

        void Interface_NetworkAddressChanged(object sender, EventArgs e)
        {
            if (restClient.NotifyIPAddress(ethernet_J11D.Interface.NetworkInterface.IPAddress))
                Debug.Print("Notification of IP Address changing for Device "
                    + restClient.Device.Name + " successful");
            else
                Debug.Print("Notification of IP Address changing for Device "
                    + restClient.Device.Name + " failed");
        }

        void SetLocalTime()
        {
            TimeService.SystemTimeChanged += TimeService_SystemTimeChanged;
            TimeService.TimeSyncFailed += TimeService_TimeSyncFailed;
            TimeService.Settings = NtpClient.TimeServiceSetting();
            TimeService.Start();
        }

        void RegisterSensor()
        {
            if (!isRegistered)
            {
                while (!restClient.RegisterSensor())
                {
                    Debug.Print("Registration failed");
                    continue;
                }
                Debug.Print("Temp Sensor 1 ID: " + ((Sensor)restClient.Sensors[0]).Id);
                Debug.Print("Temp Sensor 2 ID: " + ((Sensor)restClient.Sensors[1]).Id);
                Debug.Print("Registration successful");
                isRegistered = true;
            }
        }

        void TimeService_TimeSyncFailed(object sender, TimeSyncFailedEventArgs e)
        {
            Debug.Print("Error synchronizing system time with NTP server: " + e.ErrorCode);
        }

        void TimeService_SystemTimeChanged(object sender, SystemTimeChangedEventArgs e)
        {
            Debug.Print("Network time received.");
            Debug.Print(DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"));
            if (!timeSynchronized)
                timeSynchronized = true;
        }

        void NetworkConnect()
        {
            try
            {
                NetworkInterface[] netif = NetworkInterface.GetAllNetworkInterfaces();

                ethernet_J11D.UseDHCP();
                if (!netif[0].IsDhcpEnabled)
                    netif[0].EnableDhcp();  // This function is blocking
                else
                    netif[0].RenewDhcpLease();  // This function is blocking
                Debug.Print("Network settings:");
                Debug.Print("IP Address: " + netif[0].IPAddress);
                Debug.Print("Subnet Mask: " + netif[0].SubnetMask);
                Debug.Print("Default Getway: " + netif[0].GatewayAddress);
                Debug.Print("DNS Server: " + netif[0].DnsAddresses[0]);

                isNetworkUp = true;
            }
            catch
            {
                isNetworkUp = false;
                Debug.Print("DHCP Faild");
            }
        }

        // set up sd card event
        void InitializeSDCard()
        {
            sdCard.SDCardMounted += sdCard_SDCardMounted;
            sdCard.SDCardUnmounted += sdCard_SDCardUnmounted;
        }

        void sdCard_SDCardUnmounted(GTM.GHIElectronics.SDCard sender)
        {
            Debug.Print("The SD card has been unmounted");
            Debug.Print("DO NOT try to access it without mounting it again first");
        }

        void sdCard_SDCardMounted(GTM.GHIElectronics.SDCard sender, StorageDevice SDCard)
        {
            Debug.Print("SD card has been successfully mounted. You can now read/write/create/delete files");
            Debug.Print("Unmount before removing");
        }

        // set up senor event and initialize the timer
        void InitializeSensor()
        {
            this.temperatureHumidity.MeasurementComplete += this.TemperatureHumidityMeasurementComplete;
            this.temperatureCheckTimer = new Timer(2000);
            this.temperatureCheckTimer.Tick += temperatureCheckTimer_Tick;
            this.temperatureCheckTimer.Start();
            this.sendTimer = new Timer(4000);
            this.sendTimer.Tick += sendTimer_Tick;
            this.sendTimer.Start();
        }

        void temperatureCheckTimer_Tick(Timer timer)
        {
            this.temperatureHumidity.RequestMeasurement();
        }

        // once the measurement is complete, record it
        void TemperatureHumidityMeasurementComplete(GTM.Seeed.TemperatureHumidity sender,
            double temperature, double relativeHumidity)
        {
            Debug.Print("Temperature: " + temperature +
                ", Relative Humidity: " + relativeHumidity);

            TimedData tempData = new TimedData(
                DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"),
                temperature.ToString(), relativeHumidity.ToString());

            sensorData.Add(tempData);

            if (sdCard.IsCardInserted && sensorData.Count >= payloadSize)
            {
                if (!sdCard.IsCardMounted)
                    sdCard.MountSDCard();

                // record the time span of the historic data
                if (firstTime.Equals(string.Empty))
                    firstTime = ((TimedData)sensorData[0]).DataTime;
                lastTime = ((TimedData)sensorData[sensorData.Count - 1]).DataTime;

                string filePath = sdCard.GetStorageDevice().RootDirectory + @"\data.log";
                if (!isStoredBefore && IsDataStored("data.log"))
                    restClient.DeleteFile(filePath);

                // record the sensor data into a "data.log" in Micro SD Card
                restClient.AppendFile(filePath, sensorData);
                sensorData.Clear();
                sdCard.UnmountSDCard();

                if (!isStoredBefore)
                    isStoredBefore = true;
            }
        }

        // once the sending timer event is raised, send the data
        void sendTimer_Tick(Timer timer)
        {
            Debug.Print("send data here");

            if (isNetworkUp)
            {
                if (IsDataStored("data.log"))
                {
                    string rootDirectory = sdCard.GetStorageDevice().RootDirectory;

                    if (isStoredBefore)
                    {
                        if (restClient.SendHistoricData(
                            rootDirectory + @"\data.log", firstTime,
                            lastTime, "MyTempSensor1"))
                        {
                            restClient.DeleteFile(rootDirectory + @"\data.log");
                            firstTime = lastTime = string.Empty;
                            Debug.Print("historic data sending successful");
                        }
                        else
                            Debug.Print("historic data sending failed");
                    }
                    else
                        restClient.DeleteFile(rootDirectory + @"\data.log");
                }
                else if (sensorData.Count >= payloadSize)
                {
                    if (restClient.SendCurrentData(sensorData,
                        ((TimedData)sensorData[0]).DataTime,
                        ((TimedData)sensorData[sensorData.Count - 1]).DataTime,
                        "MyTempSensor1"))
                    {
                        sensorData.Clear();
                        Debug.Print("current data sending successful");
                    }
                    else
                        Debug.Print("current data sending failed");
                }
            }
            else
                Debug.Print("Please check the network connection.");
        }

        bool IsDataStored(string fileName)
        {
            if (sdCard.IsCardInserted)
            {
                if (!sdCard.IsCardMounted)
                    sdCard.MountSDCard();

                foreach (string s in sdCard.GetStorageDevice().ListRootDirectoryFiles())
                {
                    if (s.Equals(fileName))
                        return true;
                }
                return false;
            }
            else
                return false;
        }
    }
}